
import sys
import os
import requests
import json
import time
from typing import Dict, Any

from oss存储对象等调用 import oss文件上传

# os.environ["API_KEY"]="替换为自己的apikey或者在环境中进行配置"

####注意：以下模型需要配合oss文件存储一起使用！！


def dashscope_face_detect(
        image_url: str,
        dashscope_api_key: str=os.getenv("API_KEY")
) -> Dict[str, Any]:
    """
    调用 DashScope 人脸检测接口，校验图像是否符合视频生成要求
    返回检测结果（含是否有效、人脸坐标、置信度等）
    """
    # 1. 校验输入
    if not image_url.strip():
        raise ValueError("人脸检测：图像 URL 不能为空")
    if not dashscope_api_key.strip():
        raise ValueError("人脸检测：DashScope API Key 不能为空")

    # 2. 构造请求参数
    detect_url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/face-detect"
    headers = {
        "Authorization": f"Bearer {dashscope_api_key}",
        "Content-Type": "application/json"
    }
    request_body = {
        "model": "liveportrait-detect",  # 固定检测模型名
        "input": {"image_url": image_url.strip()}
    }

    # 3. 发送 POST 请求
    try:
        response = requests.post(
            url=detect_url,
            headers=headers,
            data=json.dumps(request_body),
            timeout=30
        )
        response.raise_for_status()  # 4xx/5xx 状态码抛异常
    except requests.exceptions.RequestException as e:
        raise Exception(f"人脸检测请求失败：{str(e)}") from e

    # 4. 解析响应（检测结果）
    try:
        detect_result = response.json()
    except json.JSONDecodeError as e:
        raise json.JSONDecodeError(f"人脸检测响应解析失败：{str(e)}，原始响应：{response.text}") from e
    # 5. 提取核心检测字段（重点：用 pass 和 message 字段判断）
    output_data = detect_result.get("output", {})
    is_pass = output_data.get("pass", False)  # 核心判断：是否通过检测
    detect_message = output_data.get("message", "未知检测信息")  # 检测说明/失败原因

    # 6. 校验检测结果是否有效（必须包含 pass 字段）
    if "pass" not in output_data:
        raise ValueError(f"人脸检测响应格式异常，未找到 'pass' 字段，响应：{detect_result}")

    # 7. 若未通过检测，抛出异常终止流程
    if not is_pass:
        raise Exception(f"图像未通过人脸检测：{detect_message}，请更换符合要求的图像")

    # 8. 检测通过，返回完整结果
    print(f"✅ 人脸检测通过！检测信息：")
    print(f"   - 是否通过：{is_pass}")
    print(f"   - 检测说明：{detect_message}")
    return detect_result


def dashscope_image2video(
        audio_url: str,
        image_url: str,
        api_key: str=os.getenv("API_KEY"),
        template_id: str = "normal",
        eye_move_freq: float = 0.5,
        video_fps: int = 30,
        mouth_move_strength: float = 1.0,
        paste_back: bool = True,
        head_move_strength: float = 0.7
) -> Dict[str, Any]:
    """
    调用阿里云 DashScope LivePortrait 接口，根据输入的图像和音频生成视频，返回接口响应

    参数说明：
        audio_url (str): 音频文件的公网可访问 URL（需符合 DashScope 要求，如格式为 WAV 等）
        image_url (str): 图像文件的公网可访问 URL（需符合 DashScope 要求，如格式为 JPG/PNG 等）
        api_key (str): 阿里云 DashScope 的 API Key（需提前在阿里云控制台申请，获取地址：https://dashscope.console.aliyun.com/）
        template_id (str, 可选): 视频生成模板 ID，默认 "normal"（DashScope 支持的模板需参考官方文档）
        eye_move_freq (float, 可选): 眼睛动效频率，默认 0.5（范围通常为 0-1，值越大动效越频繁）
        video_fps (int, 可选): 生成视频的帧率，默认 30（常见可选值：24、30、60，需符合接口限制）
        mouth_move_strength (float, 可选): 嘴部动效强度，默认 1.0（范围通常为 0-1，值越大动效越明显）
        paste_back (bool, 可选): 是否保留原图背景，默认 True（True 保留背景，False 可能去除背景）
        head_move_strength (float, 可选): 头部动效强度，默认 0.7（范围通常为 0-1，值越大动效越明显）

    返回值：
        Dict[str, Any]: 接口响应的 JSON 解析结果（包含任务 ID、状态等信息，具体格式参考 DashScope 官方文档）

    异常抛出：
        ValueError: 输入参数为空或格式非法时抛出
        requests.exceptions.RequestException: 网络请求失败（如超时、连接错误）时抛出
        json.JSONDecodeError: 接口响应无法解析为 JSON 时抛出
    """
    # 1. 参数合法性校验
    if not audio_url.strip():
        raise ValueError("音频文件地址（audio_url）不能为空")
    if not image_url.strip():
        raise ValueError("图像文件地址（image_url）不能为空")
    if not api_key.strip():
        raise ValueError("DashScope API Key（api_key）不能为空，请从阿里云控制台获取")

    # 校验数值型参数范围（基于 DashScope 常见限制，具体以官方文档为准）
    if not (0 <= eye_move_freq <= 1):
        raise ValueError(f"眼睛动效频率（eye_move_freq）需在 0-1 之间，当前值：{eye_move_freq}")
    if not (0 <= mouth_move_strength <= 1):
        raise ValueError(f"嘴部动效强度（mouth_move_strength）需在 0-1 之间，当前值：{mouth_move_strength}")
    if not (0 <= head_move_strength <= 1):
        raise ValueError(f"头部动效强度（head_move_strength）需在 0-1 之间，当前值：{head_move_strength}")
    if video_fps not in (24, 30, 60):
        raise ValueError(f"视频帧率（video_fps）仅支持 24/30/60，当前值：{video_fps}")

    # 2. 构造请求参数
    api_url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis/"
    headers = {
        "X-DashScope-Async": "enable",  # 启用异步模式（生成视频为异步任务，需后续查询结果）
        "Authorization": f"Bearer {api_key}",  # 身份认证头
        "Content-Type": "application/json"  # 请求体为 JSON 格式
    }
    request_body = {
        "model": "liveportrait",  # 固定模型名（DashScope 图像生成视频的模型）
        "input": {
            "image_url": image_url.strip(),
            "audio_url": audio_url.strip()
        },
        "parameters": {
            "template_id": template_id,
            "eye_move_freq": eye_move_freq,
            "video_fps": video_fps,
            "mouth_move_strength": mouth_move_strength,
            "paste_back": paste_back,
            "head_move_strength": head_move_strength
        }
    }

    # 3. 发送 HTTP POST 请求
    try:
        response = requests.post(
            url=api_url,
            headers=headers,
            data=json.dumps(request_body),  # 将字典转为 JSON 字符串
            timeout=30  # 超时时间（单位：秒，可根据网络情况调整）
        )
        response.raise_for_status()
        result = response.json()
        task_id = result.get("output", {}).get("task_id")
        if not task_id:
            raise ValueError(f"未获取到 Task ID，响应：{result}")
        return task_id
    except requests.exceptions.RequestException as e:
        raise requests.exceptions.RequestException(f"接口请求失败：{str(e)}") from e

    # 4. 解析响应结果（转为 JSON 格式）
    try:
        response_json = response.json()
    except json.JSONDecodeError as e:
        raise json.JSONDecodeError(f"响应解析失败（非 JSON 格式）：{str(e)}，原始响应：{response.text}") from e

    # 5. 返回响应结果
    return response_json
def get_task_result(
        task_id: str,
        dashscope_api_key: str = os.getenv("API_KEY"),
        check_interval: int = 10,#默认10秒一次
        max_retries: int = 10  # 默认为10，查询10次
) -> dict:
    query_url = f"https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}"
    headers = {"Authorization": f"Bearer {dashscope_api_key}"}

    retry_count = 0
    while retry_count < max_retries:
        try:
            # 1. 发送查询请求
            response = requests.get(query_url, headers=headers, timeout=30)  # 连接超时30秒
            response.raise_for_status()  # HTTP状态码非200（如404/500）会抛异常
            task_result = response.json()

            # 2. 提取任务状态（按DashScope API实际返回格式）
            output_data = task_result.get("output", {})
            task_status = output_data.get("task_status", "").upper()
            print(f"查询任务：状态 = {task_status}，完整响应：{task_result}")  # 新增：打印完整响应，方便排查格式

            # 3. 按状态返回/抛异常
            if task_status == "SUCCEEDED":
                # 关键修改：按实际返回路径提取 video_url → output.results.video_url
                video_url = output_data.get("results", {}).get("video_url")
                if not video_url:
                    # 错误提示中增加 output_data 打印，方便后续排查（可选但推荐）
                    raise Exception(f"任务成功但未获取到视频URL！当前output数据：{output_data}")
                return video_url
            elif task_status == "FAILED":
                # 失败：抛异常（包含错误信息）
                error_msg = output_data.get("error", {}).get("message", "未知错误")
                raise Exception(f"任务失败：{error_msg}")
            elif task_status in ("RUNNING", "PENDING"):
                # 未完成：等待后重试（仅在max_retries>1时重试）
                if retry_count < max_retries - 1:  # 不是最后一次重试，才等待
                    time.sleep(check_interval)
                retry_count += 1
            else:
                # 未知状态：抛异常
                raise Exception(f"未知任务状态：{task_status}")

        except requests.exceptions.RequestException as e:
            # 网络错误（如连接超时、DNS失败）：抛异常
            raise Exception(f"网络请求失败：{str(e)}") from e

    # 循环结束：若仍未成功，返回当前任务状态（而非直接抛超时）
    # 这里需要重新查询一次，获取最新状态并返回
    response = requests.get(query_url, headers=headers, timeout=30)
    response.raise_for_status()
    return response.json()


def download_file_from_url(file_url, save_path="downloaded_video.mp4"):
    """
    直接通过URL下载文件到本地（无需JSON解析）
    :param file_url: 待下载的文件URL（你提供的video_url）
    :param save_path: 本地保存路径（默认：当前目录下的downloaded_video.mp4）
    """
    try:
        # 1. 验证URL是否合法（简单判断是否以http/https开头）
        if not file_url.startswith(("http://", "https://")):
            raise ValueError(f"无效的URL：{file_url}（需以http://或https://开头）")

        print(f"开始下载：{file_url}")
        print(f"保存路径：{save_path}")

        # 2. 发送GET请求（stream=True分块下载，避免内存溢出）
        response = requests.get(
            file_url,
            stream=True,
            timeout=30,  # 超时时间30秒，可根据网络调整
            headers={"User-Agent": "Mozilla/5.0"}  # 模拟浏览器请求，避免部分服务器拦截
        )
        response.raise_for_status()  # 若请求失败（如404/403/500），直接抛出错误

        # 3. 写入本地文件（二进制模式wb，兼容视频/图片等非文本文件）
        total_size = int(response.headers.get("Content-Length", 0))  # 文件总大小（字节）
        downloaded_size = 0  # 已下载大小

        with open(save_path, "wb") as f:
            # 每次下载1MB（1024*1024字节），可调整
            for chunk in response.iter_content(chunk_size=1024 * 1024):
                if chunk:  # 过滤空块
                    f.write(chunk)
                    f.flush()  # 强制刷新缓冲区
                    downloaded_size += len(chunk)

                    # 可选：显示下载进度（百分比）
                    if total_size > 0:
                        progress = (downloaded_size / total_size) * 100
                        print(f"\r下载进度：{progress:.1f}%", end="")

        print(f"\n下载完成！文件大小：{downloaded_size / (1024 * 1024):.2f} MB")

    except ValueError as e:
        print(f"参数错误：{e}")
    except requests.exceptions.RequestException as e:
        print(f"下载失败（网络/URL问题）：{e}")
        # 常见请求错误说明：
        # - 404：URL不存在（可能链接过期或拼写错误）
        # - 403：没有访问权限（URL签名失效或IP被拦截）
        # - 超时：网络不稳定或服务器响应慢
    except Exception as e:
        print(f"未知错误：{e}")

if __name__ == '__main__':
    audio_url = oss文件上传.upload_local_file_to_oss("test.mp3")
    image_url = oss文件上传.upload_local_file_to_oss("test.jpeg")
    dashscope_face_detect(image_url=image_url)
    task_id = dashscope_image2video(
        audio_url=audio_url,
        image_url=image_url,
    )
    video_url=get_task_result(task_id)
    download_file_from_url(video_url)



